// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/compositor/callback_layer_animation_observer.h"

#include "base/bind.h"
#include "base/macros.h"
#include "base/memory/scoped_vector.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/compositor/layer_animation_sequence.h"
#include "ui/compositor/test/layer_animation_observer_test_api.h"

namespace ui {
namespace test {

    // Simple class that tracks whether callbacks were invoked and when.
    class TestCallbacks {
    public:
        TestCallbacks();
        virtual ~TestCallbacks();

        void ResetCallbackObservations();

        void set_should_delete_observer_on_animations_ended(
            bool should_delete_observer_on_animations_ended)
        {
            should_delete_observer_on_animations_ended_ = should_delete_observer_on_animations_ended;
        }

        bool animations_started() const { return animations_started_; }

        bool animations_ended() const { return animations_ended_; }

        virtual void AnimationsStarted(const CallbackLayerAnimationObserver&);

        virtual bool AnimationsEnded(const CallbackLayerAnimationObserver&);

        testing::AssertionResult StartedEpochIsBeforeEndedEpoch();

    private:
        // Monotonic counter that tracks the next time snapshot.
        int next_epoch_ = 0;

        // Is true when AnimationsStarted() has been called.
        bool animations_started_ = false;

        // Relative time snapshot of when AnimationsStarted() was last called.
        int animations_started_epoch_ = -1;

        // Is true when AnimationsEnded() has been called.
        bool animations_ended_ = false;

        // Relative time snapshot of when AnimationsEnded() was last called.
        int animations_ended_epoch_ = -1;

        // The return value for AnimationsEnded().
        bool should_delete_observer_on_animations_ended_ = false;

        DISALLOW_COPY_AND_ASSIGN(TestCallbacks);
    };

    TestCallbacks::TestCallbacks() { }

    TestCallbacks::~TestCallbacks() { }

    void TestCallbacks::ResetCallbackObservations()
    {
        next_epoch_ = 0;
        animations_started_ = false;
        animations_started_epoch_ = -1;
        animations_ended_ = false;
        animations_ended_epoch_ = -1;
        should_delete_observer_on_animations_ended_ = false;
    }

    void TestCallbacks::AnimationsStarted(const CallbackLayerAnimationObserver&)
    {
        animations_started_ = true;
        animations_started_epoch_ = next_epoch_++;
    }

    bool TestCallbacks::AnimationsEnded(const CallbackLayerAnimationObserver&)
    {
        animations_ended_ = true;
        animations_ended_epoch_ = next_epoch_++;
        return should_delete_observer_on_animations_ended_;
    }

    testing::AssertionResult TestCallbacks::StartedEpochIsBeforeEndedEpoch()
    {
        if (animations_started_epoch_ < animations_ended_epoch_) {
            return testing::AssertionSuccess();
        } else {
            return testing::AssertionFailure()
                << "The started epoch=" << animations_started_epoch_
                << " is NOT before the ended epoch=" << animations_ended_epoch_;
        }
    }

    // A child of TestCallbacks that can explicitly delete a
    // CallbackLayerAnimationObserver in the AnimationsStarted() or
    // AnimationsEnded() callback.
    class TestCallbacksThatExplicitlyDeletesObserver : public TestCallbacks {
    public:
        TestCallbacksThatExplicitlyDeletesObserver();

        void set_observer_to_delete_in_animation_started(
            CallbackLayerAnimationObserver* observer)
        {
            observer_to_delete_in_animation_started_ = observer;
        }

        void set_observer_to_delete_in_animation_ended(
            CallbackLayerAnimationObserver* observer)
        {
            observer_to_delete_in_animation_ended_ = observer;
        }

        // TestCallbacks:
        void AnimationsStarted(
            const CallbackLayerAnimationObserver& observer) override;
        bool AnimationsEnded(const CallbackLayerAnimationObserver& observer) override;

    private:
        // The observer to delete, if non-NULL, in AnimationsStarted().
        CallbackLayerAnimationObserver* observer_to_delete_in_animation_started_ = nullptr;

        // The observer to delete, if non-NULL, in AnimationsEnded().
        CallbackLayerAnimationObserver* observer_to_delete_in_animation_ended_ = nullptr;

        DISALLOW_COPY_AND_ASSIGN(TestCallbacksThatExplicitlyDeletesObserver);
    };

    TestCallbacksThatExplicitlyDeletesObserver::
        TestCallbacksThatExplicitlyDeletesObserver() { }

    void TestCallbacksThatExplicitlyDeletesObserver::AnimationsStarted(
        const CallbackLayerAnimationObserver& observer)
    {
        if (observer_to_delete_in_animation_started_)
            delete observer_to_delete_in_animation_started_;
        TestCallbacks::AnimationsStarted(observer);
    }

    bool TestCallbacksThatExplicitlyDeletesObserver::AnimationsEnded(
        const CallbackLayerAnimationObserver& observer)
    {
        if (observer_to_delete_in_animation_ended_)
            delete observer_to_delete_in_animation_ended_;
        return TestCallbacks::AnimationsEnded(observer);
    }

    // A test specific CallbackLayerAnimationObserver that will set a bool when
    // destroyed.
    class TestCallbackLayerAnimationObserver
        : public CallbackLayerAnimationObserver {
    public:
        TestCallbackLayerAnimationObserver(
            AnimationStartedCallback animation_started_callback,
            AnimationEndedCallback animation_ended_callback,
            bool* destroyed);

        TestCallbackLayerAnimationObserver(
            AnimationStartedCallback animation_started_callback,
            bool should_delete_observer,
            bool* destroyed);

        TestCallbackLayerAnimationObserver(
            AnimationEndedCallback animation_ended_callback,
            bool* destroyed);

        ~TestCallbackLayerAnimationObserver() override;

    private:
        bool* destroyed_;

        DISALLOW_COPY_AND_ASSIGN(TestCallbackLayerAnimationObserver);
    };

    TestCallbackLayerAnimationObserver::TestCallbackLayerAnimationObserver(
        AnimationStartedCallback animation_started_callback,
        AnimationEndedCallback animation_ended_callback,
        bool* destroyed)
        : CallbackLayerAnimationObserver(animation_started_callback,
            animation_ended_callback)
        , destroyed_(destroyed)
    {
        if (destroyed_)
            (*destroyed_) = false;
    }

    TestCallbackLayerAnimationObserver::TestCallbackLayerAnimationObserver(
        AnimationStartedCallback animation_started_callback,
        bool should_delete_observer,
        bool* destroyed)
        : CallbackLayerAnimationObserver(animation_started_callback,
            should_delete_observer)
        , destroyed_(destroyed)
    {
        if (destroyed_)
            (*destroyed_) = false;
    }

    TestCallbackLayerAnimationObserver::TestCallbackLayerAnimationObserver(
        AnimationEndedCallback animation_ended_callback,
        bool* destroyed)
        : CallbackLayerAnimationObserver(animation_ended_callback)
        , destroyed_(destroyed)
    {
        if (destroyed_)
            (*destroyed_) = false;
    }

    TestCallbackLayerAnimationObserver::~TestCallbackLayerAnimationObserver()
    {
        if (destroyed_)
            (*destroyed_) = true;
    }

    class CallbackLayerAnimationObserverTest : public testing::Test {
    public:
        CallbackLayerAnimationObserverTest();
        ~CallbackLayerAnimationObserverTest() override;

    protected:
        // Creates a LayerAnimationSequence.  The lifetime of the sequence will be
        // managed by this.
        LayerAnimationSequence* CreateLayerAnimationSequence();

        scoped_ptr<TestCallbacks> callbacks_;

        scoped_ptr<CallbackLayerAnimationObserver> observer_;

        scoped_ptr<LayerAnimationObserverTestApi> observer_test_api_;

        // List of managaged sequences created by CreateLayerAnimationSequence() that
        // need to be destroyed.
        ScopedVector<LayerAnimationSequence> sequences_;

    private:
        DISALLOW_COPY_AND_ASSIGN(CallbackLayerAnimationObserverTest);
    };

    CallbackLayerAnimationObserverTest::CallbackLayerAnimationObserverTest()
        : callbacks_(new TestCallbacks())
        , observer_(new CallbackLayerAnimationObserver(
              base::Bind(&TestCallbacks::AnimationsStarted,
                  base::Unretained(callbacks_.get())),
              base::Bind(&TestCallbacks::AnimationsEnded,
                  base::Unretained(callbacks_.get()))))
        , observer_test_api_(new LayerAnimationObserverTestApi(observer_.get()))
    {
    }

    CallbackLayerAnimationObserverTest::~CallbackLayerAnimationObserverTest()
    {
        observer_test_api_.reset();
        // The |observer_| will detach from all attached sequences upon destruction so
        // we need to explicitly delete the |observer_| before the |sequences_| and
        // |callbacks_|.
        observer_.reset();
    }

    LayerAnimationSequence*
    CallbackLayerAnimationObserverTest::CreateLayerAnimationSequence()
    {
        LayerAnimationSequence* sequence = new LayerAnimationSequence();
        sequences_.push_back(sequence);
        return sequence;
    }

    TEST(CallbackLayerAnimationObserverDestructionTest, VerifyFalseAutoDelete)
    {
        TestCallbacks callbacks;
        callbacks.set_should_delete_observer_on_animations_ended(false);

        bool is_destroyed = false;

        TestCallbackLayerAnimationObserver* observer = new TestCallbackLayerAnimationObserver(
            base::Bind(&TestCallbacks::AnimationsStarted,
                base::Unretained(&callbacks)),
            false, &is_destroyed);
        observer->SetActive();

        EXPECT_FALSE(is_destroyed);
        delete observer;
    }

    TEST(CallbackLayerAnimationObserverDestructionTest, VerifyTrueAutoDelete)
    {
        TestCallbacks callbacks;
        callbacks.set_should_delete_observer_on_animations_ended(false);

        bool is_destroyed = false;

        TestCallbackLayerAnimationObserver* observer = new TestCallbackLayerAnimationObserver(
            base::Bind(&TestCallbacks::AnimationsStarted,
                base::Unretained(&callbacks)),
            true, &is_destroyed);
        observer->SetActive();

        EXPECT_TRUE(is_destroyed);
    }

    TEST(CallbackLayerAnimationObserverDestructionTest,
        AnimationEndedReturnsFalse)
    {
        TestCallbacks callbacks;
        callbacks.set_should_delete_observer_on_animations_ended(false);

        bool is_destroyed = false;

        TestCallbackLayerAnimationObserver* observer = new TestCallbackLayerAnimationObserver(
            base::Bind(&TestCallbacks::AnimationsStarted,
                base::Unretained(&callbacks)),
            base::Bind(&TestCallbacks::AnimationsEnded,
                base::Unretained(&callbacks)),
            &is_destroyed);
        observer->SetActive();

        EXPECT_FALSE(is_destroyed);
        delete observer;
    }

    TEST(CallbackLayerAnimationObserverDestructionTest, AnimationEndedReturnsTrue)
    {
        TestCallbacks callbacks;
        callbacks.set_should_delete_observer_on_animations_ended(true);

        bool is_destroyed = false;

        TestCallbackLayerAnimationObserver* observer = new TestCallbackLayerAnimationObserver(
            base::Bind(&TestCallbacks::AnimationsStarted,
                base::Unretained(&callbacks)),
            base::Bind(&TestCallbacks::AnimationsEnded,
                base::Unretained(&callbacks)),
            &is_destroyed);
        observer->SetActive();

        EXPECT_TRUE(is_destroyed);
    }

    TEST_F(CallbackLayerAnimationObserverTest, VerifyInitialState)
    {
        EXPECT_FALSE(observer_->active());
        EXPECT_EQ(0, observer_->aborted_count());
        EXPECT_EQ(0, observer_->successful_count());

        EXPECT_FALSE(callbacks_->animations_started());
        EXPECT_FALSE(callbacks_->animations_ended());
    }

    // Verifies that the CallbackLayerAnimationObserver is robust to explicit
    // deletes caused as a side effect of calling the AnimationsStartedCallback()
    // when there are no animation sequences attached. This test also guards against
    // heap-use-after-free errors.
    TEST_F(
        CallbackLayerAnimationObserverTest,
        ExplicitlyDeleteObserverInAnimationStartedCallbackWithNoSequencesAttached)
    {
        TestCallbacksThatExplicitlyDeletesObserver callbacks;
        callbacks.set_should_delete_observer_on_animations_ended(true);

        bool is_destroyed = false;

        TestCallbackLayerAnimationObserver* observer = new TestCallbackLayerAnimationObserver(
            base::Bind(&TestCallbacks::AnimationsStarted,
                base::Unretained(&callbacks)),
            base::Bind(&TestCallbacks::AnimationsEnded,
                base::Unretained(&callbacks)),
            &is_destroyed);

        callbacks.set_observer_to_delete_in_animation_started(observer);

        observer->SetActive();

        EXPECT_TRUE(is_destroyed);
    }

    // Verifies that the CallbackLayerAnimationObserver is robust to explicit
    // deletes caused as a side effect of calling the AnimationsStartedCallback()
    // when there are some animation sequences attached. This test also guards
    // against heap-use-after-free errors.
    TEST_F(
        CallbackLayerAnimationObserverTest,
        ExplicitlyDeleteObserverInAnimationStartedCallbackWithSomeSequencesAttached)
    {
        LayerAnimationSequence* sequence_1 = CreateLayerAnimationSequence();
        LayerAnimationSequence* sequence_2 = CreateLayerAnimationSequence();

        TestCallbacksThatExplicitlyDeletesObserver callbacks;
        callbacks.set_should_delete_observer_on_animations_ended(true);

        bool is_destroyed = false;

        TestCallbackLayerAnimationObserver* observer = new TestCallbackLayerAnimationObserver(
            base::Bind(&TestCallbacks::AnimationsStarted,
                base::Unretained(&callbacks)),
            base::Bind(&TestCallbacks::AnimationsEnded,
                base::Unretained(&callbacks)),
            &is_destroyed);

        observer_test_api_->AttachedToSequence(sequence_1);
        observer_test_api_->AttachedToSequence(sequence_2);
        observer_->OnLayerAnimationStarted(sequence_1);
        observer_->OnLayerAnimationStarted(sequence_2);

        callbacks.set_observer_to_delete_in_animation_started(observer);

        observer->SetActive();

        EXPECT_TRUE(is_destroyed);
    }

    // Verifies that a 'true' return value for AnimationEndedCallback is ignored if
    // the CallbackLayerAnimationObserver is explicitly deleted as a side effect of
    // calling the AnimationEndedCallback. This test also guards against
    // heap-use-after-free errors.
    TEST_F(CallbackLayerAnimationObserverTest,
        IgnoreTrueReturnValueForAnimationEndedCallbackIfExplicitlyDeleted)
    {
        TestCallbacksThatExplicitlyDeletesObserver callbacks;
        callbacks.set_should_delete_observer_on_animations_ended(true);

        bool is_destroyed = false;

        TestCallbackLayerAnimationObserver* observer = new TestCallbackLayerAnimationObserver(
            base::Bind(&TestCallbacks::AnimationsStarted,
                base::Unretained(&callbacks)),
            base::Bind(&TestCallbacks::AnimationsEnded,
                base::Unretained(&callbacks)),
            &is_destroyed);

        callbacks.set_observer_to_delete_in_animation_ended(observer);

        observer->SetActive();

        EXPECT_TRUE(is_destroyed);
    }

    TEST_F(CallbackLayerAnimationObserverTest,
        SetActiveWhenNoSequencesWereAttached)
    {
        observer_->SetActive();

        EXPECT_FALSE(observer_->active());
        EXPECT_TRUE(callbacks_->animations_started());
        EXPECT_TRUE(callbacks_->animations_ended());
        EXPECT_TRUE(callbacks_->StartedEpochIsBeforeEndedEpoch());
    }

    TEST_F(CallbackLayerAnimationObserverTest,
        SetActiveWhenAllSequencesAreAttachedButNoneWereStarted)
    {
        LayerAnimationSequence* sequence_1 = CreateLayerAnimationSequence();
        LayerAnimationSequence* sequence_2 = CreateLayerAnimationSequence();

        observer_test_api_->AttachedToSequence(sequence_1);
        observer_test_api_->AttachedToSequence(sequence_2);

        observer_->SetActive();

        EXPECT_TRUE(observer_->active());
        EXPECT_FALSE(callbacks_->animations_started());
        EXPECT_FALSE(callbacks_->animations_ended());
    }

    TEST_F(CallbackLayerAnimationObserverTest,
        SetActiveWhenAllSequencesAreAttachedAndOnlySomeWereStarted)
    {
        LayerAnimationSequence* sequence_1 = CreateLayerAnimationSequence();
        LayerAnimationSequence* sequence_2 = CreateLayerAnimationSequence();

        observer_test_api_->AttachedToSequence(sequence_1);
        observer_test_api_->AttachedToSequence(sequence_2);
        observer_->OnLayerAnimationStarted(sequence_1);

        observer_->SetActive();

        EXPECT_TRUE(observer_->active());
        EXPECT_FALSE(callbacks_->animations_started());
        EXPECT_FALSE(callbacks_->animations_ended());
    }

    TEST_F(CallbackLayerAnimationObserverTest,
        SetActiveWhenAllSequencesAreAttachedAndOnlySomeWereCompleted)
    {
        LayerAnimationSequence* sequence_1 = CreateLayerAnimationSequence();
        LayerAnimationSequence* sequence_2 = CreateLayerAnimationSequence();

        observer_test_api_->AttachedToSequence(sequence_1);
        observer_test_api_->AttachedToSequence(sequence_2);
        observer_->OnLayerAnimationStarted(sequence_1);
        observer_->OnLayerAnimationEnded(sequence_1);

        observer_->SetActive();

        EXPECT_TRUE(observer_->active());
        EXPECT_FALSE(callbacks_->animations_started());
        EXPECT_FALSE(callbacks_->animations_ended());
    }

    TEST_F(CallbackLayerAnimationObserverTest,
        SetActiveAfterAllSequencesWereStartedButNoneWereCompleted)
    {
        LayerAnimationSequence* sequence_1 = CreateLayerAnimationSequence();
        LayerAnimationSequence* sequence_2 = CreateLayerAnimationSequence();

        observer_test_api_->AttachedToSequence(sequence_1);
        observer_test_api_->AttachedToSequence(sequence_2);
        observer_->OnLayerAnimationStarted(sequence_1);
        observer_->OnLayerAnimationStarted(sequence_2);

        observer_->SetActive();

        EXPECT_TRUE(observer_->active());
        EXPECT_TRUE(callbacks_->animations_started());
        EXPECT_FALSE(callbacks_->animations_ended());
    }

    TEST_F(CallbackLayerAnimationObserverTest,
        SetActiveWhenAllSequencesAreStartedAndOnlySomeWereCompleted)
    {
        LayerAnimationSequence* sequence_1 = CreateLayerAnimationSequence();
        LayerAnimationSequence* sequence_2 = CreateLayerAnimationSequence();

        observer_test_api_->AttachedToSequence(sequence_1);
        observer_test_api_->AttachedToSequence(sequence_2);
        observer_->OnLayerAnimationStarted(sequence_1);
        observer_->OnLayerAnimationStarted(sequence_2);
        observer_->OnLayerAnimationEnded(sequence_1);

        observer_->SetActive();

        EXPECT_TRUE(observer_->active());
        EXPECT_TRUE(callbacks_->animations_started());
        EXPECT_FALSE(callbacks_->animations_ended());
    }

    TEST_F(CallbackLayerAnimationObserverTest,
        SetActiveWhenAllSequencesWereCompleted)
    {
        LayerAnimationSequence* sequence_1 = CreateLayerAnimationSequence();
        LayerAnimationSequence* sequence_2 = CreateLayerAnimationSequence();

        observer_test_api_->AttachedToSequence(sequence_1);
        observer_test_api_->AttachedToSequence(sequence_2);
        observer_->OnLayerAnimationStarted(sequence_1);
        observer_->OnLayerAnimationStarted(sequence_2);
        observer_->OnLayerAnimationEnded(sequence_1);
        observer_->OnLayerAnimationEnded(sequence_2);

        observer_->SetActive();

        EXPECT_FALSE(observer_->active());
        EXPECT_TRUE(callbacks_->animations_started());
        EXPECT_TRUE(callbacks_->animations_ended());
    }

    TEST_F(CallbackLayerAnimationObserverTest,
        SetActiveAgainAfterAllSequencesWereCompleted)
    {
        LayerAnimationSequence* sequence_1 = CreateLayerAnimationSequence();
        LayerAnimationSequence* sequence_2 = CreateLayerAnimationSequence();
        LayerAnimationSequence* sequence_3 = CreateLayerAnimationSequence();
        LayerAnimationSequence* sequence_4 = CreateLayerAnimationSequence();

        observer_test_api_->AttachedToSequence(sequence_1);
        observer_test_api_->AttachedToSequence(sequence_2);
        observer_->OnLayerAnimationStarted(sequence_1);
        observer_->OnLayerAnimationStarted(sequence_2);
        observer_->OnLayerAnimationEnded(sequence_1);
        observer_->OnLayerAnimationEnded(sequence_2);

        observer_->SetActive();

        EXPECT_FALSE(observer_->active());

        observer_test_api_->AttachedToSequence(sequence_3);
        observer_test_api_->AttachedToSequence(sequence_4);

        callbacks_->ResetCallbackObservations();

        observer_->SetActive();

        EXPECT_TRUE(observer_->active());
        EXPECT_FALSE(callbacks_->animations_started());
        EXPECT_FALSE(callbacks_->animations_ended());
        EXPECT_EQ(2, observer_->successful_count());

        observer_->OnLayerAnimationStarted(sequence_3);
        observer_->OnLayerAnimationStarted(sequence_4);

        EXPECT_TRUE(observer_->active());
        EXPECT_TRUE(callbacks_->animations_started());
        EXPECT_FALSE(callbacks_->animations_ended());
        EXPECT_EQ(2, observer_->successful_count());

        observer_->OnLayerAnimationEnded(sequence_3);
        observer_->OnLayerAnimationEnded(sequence_4);

        EXPECT_FALSE(observer_->active());
        EXPECT_TRUE(callbacks_->animations_started());
        EXPECT_TRUE(callbacks_->animations_ended());
        EXPECT_EQ(4, observer_->successful_count());
    }

} // namespace test
} // namespace ui
